#!/bin/bash
echo 'install-badge-dev-env: Auto-install development environment for BadgeApp.'
echo 'See INSTALL.md for more information.'
echo
echo 'Make sure your system is up-to-date before running this!'
echo 'Set environment variable MYINSTALL_SYSTEM_INSTALL_PACKAGES=no if wanted.'

# Set configure options defaults; set their values with environment variables.
if [ "$(uname)" != 'Darwin' ] ; then  # NOT MacOS
  : "${MYINSTALL_SYSTEM_INSTALL_PREFIX:=sudo}"
else
  : "${MYINSTALL_SYSTEM_INSTALL_PREFIX:= }"
fi
: "${MYINSTALL_SYSTEM_INSTALL_PACKAGES:=yes}"
: "${MYINSTALL_RBENV:=yes}" # Should we install and use rbenv?
: "${MYINSTALL_RBENV_BUNDLER:=yes}" # Should we install and use rbenv-bundler?

if [ "$MYINSTALL_RBENV_BUNDLER" = yes ] &&
   [ "$MYINSTALL_RBENV" = no ] ; then
  echo "Error: rbenv-bundler requires rbenv."
  exit 1
fi

# If you modify this script, please check it with the "shellcheck"
# shell static analysis tool.
# The following enables some run-time error detection:
set -e


######################################################################
# Define globals and functions.
######################################################################

# This is the running list of system packages to install.
PACKAGES=''

# Add list of packages to the PACKAGES list. Rename packages as needed
add_pkg () {
  for p ; do
    if [ "$p" = '' ] || [ "$p" = '-' ] ; then continue ; fi
    if [ "$p" = 'sqlite3' ] && { [ "$manager" = 'dnf' ] || [ "$manager" = 'yum' ]; }; then
      p='sqlite'
    fi
    PACKAGES="$PACKAGES $p"
  done
}

# Return true iff $1 is a command
is_command () {
  command -v "$1" > /dev/null
}

# Given a list of commands, return the first one that exists (if any)
find_command () {
  for f ; do
    if is_command "$f" ; then
      echo "$f"
      true
      return
    fi
  done
  # None found, return something useful.
  echo UNKNOWN
  false
}

# Given the path to the user's shell init file, adds a path concatenation of
# the second and third parameters to it. Expects three parameters:
#
# $1 is the shell init file
# $2 is base part of path /home... without trailing slash
# $3 is unique end part of path
#
# returns true (0) if shell init file exists and PATH was appended to, false
#         (1) otherwise
add_to_path () {
  if [ -e "$1" ] ; then
    # shellcheck disable=SC2039
    local pattern
    # shellcheck disable=SC2001
    pattern="PATH=.*"$(echo "$3" | sed -e "s/\./\\\./g")
    if ! grep "$pattern" "$1" > /dev/null ; then
      echo "Modifying $1 to add $3 to path. NOTE: Use this command:"
      echo "  . $1"
      echo "for the environment changes to take effect in current shell."
      # shellcheck disable=SC2016
      echo "export PATH=$2/$3:"'$PATH' >> "$1"
      true
      return
    fi
  fi
  false
  return
}

can_build_ruby () {
  rbenv install -l | grep -q "^ *${ruby_version}\$"
}

######################################################################
# Main line.
######################################################################

if ! [ -f 'install-badge-dev-env' ] ; then
  echo 'Must run at top level.' >&2
  exit 1
fi

# First, figure out what package manager to use.
echo
echo 'STAGE 1: Determine the package manager to use.'

if [ "$(uname)" = 'Darwin' ] ; then  # MacOS.  Use 'brew'.
  manager='brew'
  if ! is_command brew ; then
    echo 'Downloading and installing brew.'
    brew_url='https://raw.githubusercontent.com/Homebrew/install/master/install'
    ruby -e "$(curl -fsSL $brew_url)"
    brew tap homebrew/cask
  fi
else
  # apt-get : Debian, Ubuntu
  # dnf : some Fedora
  # yum : Red Hat Enterprise Linux, CentOS, some Fedora
  # zypper : SuSE
  # emerge : Gentoo
  # pkg : *BSDs.  We're not dealing with ports vs. packages; patches welcome.
  # urpmi : Mageia/Mandriva
  # pacman : Manjaro/Arch Linux
  manager=$(find_command apt-get dnf yum zypper emerge pkg urpmi pacman)
  if [ "$manager" = 'UNKNOWN' ] ; then
    echo 'Could not find a system package manager.'
    exit 1
  fi
fi

case "$manager" in
  urpmi)  installer="$manager" ;;
  pacman) installer="$manager -S base-devel" ;;
  *)      installer="$manager install" ;;
esac
echo "Will use the installer command '$installer'"

# Now start adding packages.
echo
echo 'STAGE 2: Identifying and install system packages'

# git should already be installed, but we'll make sure of it.
is_command git || add_pkg git

# curl is helpful for installing and debugging things, so let's install it.
is_command curl || add_pkg curl

# Install a bootstrap version of Ruby, if we don't already have one.
# We'll actually install a specific version later, but this will help us
# bootstrap the installation and building of that version.
is_command ruby || add_pkg ruby

# We will build ruby via rbenv and ruby-build. This requires either those
# packages themselves, or a number of other packages to build them.
# Here are the recommended packages per the ruby-build
# instructions at <https://github.com/sstephenson/ruby-build/wiki>.
case "$manager" in
  brew)
    # We'll use the brew version. Install these to rebuild rbenv:
    # add_pkg openssl libyaml libffi
    add_pkg rbenv ruby-build graphviz postgres cmake ;;

  apt-get)
    # We'll use the system version.  If you want to use the latest one
    # on GitHub, instead install these system components first:
    # add_pkg autoconf bison build-essential libssl-dev libyaml-dev \
    #         libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev \
    #         libgdbm3 libgdbm-dev
    add_pkg autoconf bison build-essential libssl-dev libyaml-dev \
            libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev \
            libgdbm-dev postgresql-server-dev-all postgresql \
            postgresql-contrib cmake nodejs graphviz \
            pkg-config shellcheck;;
  yum|dnf)
    add_pkg gcc openssl-devel libyaml-devel libffi-devel readline-devel \
            zlib-devel gdbm-devel ncurses-devel postgresql-devel cmake npm \
            postgresql-server postgresql-contrib graphviz ;;
  zypper)
    add_pkg gcc automake gdbm-devel libffi-devel libyaml-devel \
            openssl-devel ncurses-devel readline-devel zlib-devel ;;
  pacman)
     add_pkg libffi libyaml openssl zlib graphviz ;;
  *)
     # We'll guess some packages needed.
     add_pkg gcc openssl zlib
     echo 'Warning:  You may need additional packages to rebuild ruby.' >&2 ;;
esac


if [ "$MYINSTALL_SYSTEM_INSTALL_PACKAGES" = 'yes' ] ; then
  echo 'About to install system packages with the command:'
  echo "  ${MYINSTALL_SYSTEM_INSTALL_PREFIX} $installer $PACKAGES"
  # shellcheck disable=SC2086
  if ! $MYINSTALL_SYSTEM_INSTALL_PREFIX $installer $PACKAGES; then
    echo 'Install failed. Press enter to continue anyway, control-C to stop.'
    read -r
  fi
else
  echo 'Skipping system package install.'
fi

shell_init=''
for try in .bashrc .bash_profile .zshrc .profile ; do
  if [ -f "$HOME/$try" ] ; then
    shell_init="$HOME/$try"
    break
  fi
done

case "$manager" in
  brew)
    brew services start postgres
    ;;
  yum|dnf)
    npm_path="node_modules/.bin"
    PATH="$(pwd)/$npm_path:$PATH"
    export PATH
    add_to_path "$shell_init" "$(pwd)" "$npm_path"
    sudo postgresql-setup initdb
    sudo systemctl enable postgresql
    sudo systemctl start postgresql
    ;;
  *)
    : # do nothing
esac

# Install rbenv 1.0.0 via GitHub, if it isn't already installed,
# to let us select a specific version of ruby.
echo
echo 'STAGE 3: Install and setup rbenv and ruby-build'

# Remarkably, older versions of git didn't check retrieved objects.
# Always check.  Technically we only need to set this for 'transfer',
# because fetch defaults to whatever transfer does, but let's force it.
# Receive also defaults to what 'transfer' does, but it's unlikely to have
# something different.
# This is per a recommendation about git integrity by Eric Myhre.
# We do this here, *before* we might use git to retrieve something.
echo 'Forcing git to check retrieved objects'
git config --global transfer.fsckobjects true
git config --global fetch.fsckobjects true


if is_command 'rvm' ; then
  echo 'WARNING: rvm installed, may be incompatible with rbenv.' >&2
fi

DO_LOCAL_RBENV='no'
if is_command 'rbenv' ; then
  echo 'rbenv already installed.'
  if ! rbenv --version | grep "1\..\.." > /dev/null 2>&1 ; then
    echo '  Installed rbenv version != 1.x.x'
    DO_LOCAL_RBENV='yes'
  fi
  # CircleCI has an environment which cause our tests to fail
  # so force manual install of rbenv on CircleCI.
  if [ -n "$CIRCLECI" ]; then
    DO_LOCAL_RBENV='yes'
    echo ' On CircleCI manually installing rbenv'
  fi
else
  DO_LOCAL_RBENV='yes'
  if [ "$MYINSTALL_RBENV" != 'yes' ] ; then
    echo 'Skipping rbenv install.'
  fi
fi

if [ "$DO_LOCAL_RBENV" = 'yes' ] ; then
  echo 'Downloading and installing rbenv from GitHub' >&2
  if ! [ -d ~/.rbenv ] ; then
    git clone https://github.com/rbenv/rbenv.git ~/.rbenv
  fi
  # Following performed in subshell so as not to change working directory.
  (
  cd ~/.rbenv
  git fetch
  git checkout a3fa9b73b8e6907845bdf47d2c2924c187580bdc
  )
fi

if [ "$DO_LOCAL_RBENV" = 'yes' ] ; then
  # ensure rbenv is on the PATH when running the rest of this script.
  export PATH="$HOME/.rbenv/bin:$PATH"
  # shellcheck disable=SC2016
  if add_to_path "$shell_init" "$HOME" '.rbenv/bin' ; then
    # shellcheck disable=SC2016
    echo 'eval "$(rbenv init -)"' >> "$shell_init"
  fi
  if [ ! -e "$shell_init" ] ; then
    echo 'Warning: rbenv PATH is not set up.' >&2
  fi
fi

# Install rbenv-bundler.
# This makes "bundle ..." use rbenv's version of Ruby, so we don't need
# to prefix commands with "bin/..." or "bundle exec ...":
if [ "${MYINSTALL_RBENV_BUNDLER}" = 'yes' ] && \
   ! [ -e "$HOME/.rbenv/plugins/bundler" ] ; then
  echo 'Downloading and installing rbenv-bundler from GitHub' >&2
  mkdir -p "$HOME/.rbenv/plugins/"
  git clone https://github.com/carsomyr/rbenv-bundler.git \
            "$HOME/.rbenv/plugins/bundler"
fi

# Force install Gemfile ruby version using rbenv. This may cause a compile.
echo
echo 'STAGE 4: For this project, force install fixed version of ruby'

if [ "$MYINSTALL_RBENV" = 'yes' ] && is_command rbenv ; then
  # Get ruby version from Gemfile.
  ruby_version=$(cat .ruby-version)
  if [ "$(uname)" != 'Darwin' ] && ! can_build_ruby; then
    echo 'Updating ruby-build with GitHub version.'
    if [ ! -d "$HOME/.rbenv/plugins/ruby-build" ] ; then
      git clone https://github.com/rbenv/ruby-build.git \
          "$HOME/.rbenv/plugins/ruby-build"
    else
      echo 'ruby-build directory exists, updating...'
      (cd "$HOME/.rbenv/plugins/ruby-build" && git pull)
    fi
  else
    echo 'ruby-build up to date.'
  fi
  echo "Using rbenv to locally install ruby version ${ruby_version}"
  rbenv install --skip-existing "$ruby_version"
  rbenv local "$ruby_version" # In this directory AND BELOW, use this version.
  eval "$(rbenv init -)"
else
  echo 'Skipped installing fixed version of ruby - no rbenv'
fi

echo
echo 'STAGE 5: Install gems (including bundler and Rails)'

gem sources --add https://rubygems.org  # Ensure you're getting gems here

# Install the exact bundler version specified in Gemfile.lock to avoid conflicts
bundler_version=$(grep -A 1 "^BUNDLED WITH" Gemfile.lock | tail -1 | tr -d ' ')
echo "Installing bundler version $bundler_version (per Gemfile.lock)"
gem install bundler --version="$bundler_version"

# If we're using rbenv, ensure it can find "bundle" and friends.
if is_command rbenv ; then
  rbenv rehash
fi

# Install gems using the exact bundler version
bundle "_${bundler_version}_" install

# Ensure that bin/COMMAND always runs the correct versions of gems,
# even when gems are updated later. That way you don't HAVE to
# say "bundle exec COMMAND" to load the correct libraries, you can instead
# say bin/COMMAND
# The rbenv-bundler tool uses this capability so you can simply say COMMAND
# (it builds on bundle binstubs).
# bundle binstubs

# "bundle" installs additional commands - ensure we can find them,
# by rehashing if we're using rbenv.
# This is especially important because it appears that the
# asset compilation process needs some commands to work and it doesn't
# always report unexpected failures.  Without this, later JavaScript
# may mysteriously fail because the assets aren't quite correct.
if is_command rbenv ; then
  rbenv rehash
fi

echo
echo 'STAGE 6: Set up database for development if necessary'

DB_USER="$(whoami)"
if psql -t -c "SELECT 1 FROM pg_roles WHERE rolname='${DB_USER}'" postgres \
        2>/dev/null | grep -q 1; then
    echo "PostgreSQL user ${DB_USER} already exists"
else
    echo "Creating PostgreSQL user ${DB_USER}"
    if ! sudo -u postgres createuser -s "${DB_USER}"; then
        echo "Warning: Could not create PostgreSQL user ${DB_USER}"
    fi
fi

# Is the database already set up?
db_present=unknown
if psql -d development -c "\dt" | grep schema_migrations >/dev/null 2>&1
then
  db_present=true
else
  db_present=false
fi

case "$db_present" in
false)
  echo 'Database not present. Running "bundle exec rake db:setup" to seed with dummy data'
  bundle exec rake db:setup ;;
true)
  echo 'Skipping "rake db:setup" - the database appears to be present' ;;
*)
  echo 'UNEXPECTED RESULT from checking if database is set up' >&2
  echo " $db_present" >&2 ;;
esac

echo
echo 'STAGE 7: Minor git setups.'

if ! git remote | grep -q '^upstream$' ; then
  echo 'Adding git remote "upstream"'
  git remote add upstream \
      https://github.com/coreinfrastructure/best-practices-badge.git
fi

if ! git config user.name > /dev/null ; then
  echo 'What is your human-readable name (Example: David A. Wheeler)?'
  # Ensures CircleCI doesn't get stuck
  if [ -z "$CIRCLECI" ]; then
    read -r name
  else
    name="circleci"
  fi
  git config --global user.name "$name"
  echo 'You may change it with: git config --global user.name "YOUR NAME"'
fi

if ! git config user.name > /dev/null ; then
  echo 'What is your email address?'
  # Ensures CircleCI doesn't get stuck
  if [ -z "$CIRCLECI" ]; then
    read -r email
  else
    email="circleci@circleci.com"
  fi
  git config --global user.email "$email"
  echo 'You may change it with: git config --global user.email "EMAIL ADDRESS"'
fi

echo
echo 'FINAL STAGE: Test to see if it is working'

# On initial setup the test in feed_test.rb will fail with an
# ActiveRecord::EnvironmentMismatchError
# Running db:migrate in the test environment fixes the error.
bundle exec rake db:migrate RAILS_ENV=test

# We don't want to force everyone do this, but a check on install
# if the tools already happen to be available seems reasonable.
if is_command shellcheck ; then
  echo 'Statically checking this install command'
  shellcheck install-badge-dev-env || echo 'Please review these issues.'
fi

echo 'All done! Please reopen your terminal or run'
echo '"source ~/.bashrc" to adjust your PATH.'
echo 'Then run "bundle exec rake" to check the install.'
echo 'Run "bundle exec rails server" and use a web browser'
echo 'to view localhost:3000 to see it run.'
